package operations

import (
	"github.com/sirupsen/logrus"
	"mydocker/cgroups"
	"mydocker/cgroups/resource"
	"mydocker/container"
	"mydocker/network"
	"os"
	"strconv"
	"strings"
)

// Run 启动容器，配置资源限制，并执行用户指定的命令
func Run(cmdArray []string, tty bool, res *resource.ResourceConfig, containerName, imageName, volume, net string, envs, ports []string) {
	containerID := container.GenerateContainerID() // 生成 10 位容器 ID

	parent, writePipe := container.NewParentProcess(tty, volume, containerID, imageName, envs)
	if parent == nil {
		logrus.Errorf("failed to new parent process")
		return
	}
	if err := parent.Start(); err != nil { // 即 执行 init 命令
		logrus.Errorf("parent start failed, err:%v", err)
		return
	}

	// 创建 cgroup manager，并通过调用 set 和 apply 设置资源限制并使限制在容器上生效
	cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup")
	// 删除资源限制
	//defer cgroupManager.Destroy() // 有单独的 goroutine 来处理
	// 设置资源限制
	_ = cgroupManager.Set(res)
	// 将容器进程，加入到各个 fs 挂载对应的 cgroup 中
	_ = cgroupManager.Apply(parent.Process.Pid)

	var containerIP string
	// 如果指定了网络信息则进行配置
	if net != "" {
		// 容器信息
		containerInfo := &container.ContainerInfo{
			ID:          containerID,
			Pid:         strconv.Itoa(parent.Process.Pid),
			Name:        containerName,
			PortMapping: ports,
		}
		ip, err := network.Connect(net, containerInfo)
		if err != nil {
			logrus.Errorf("network connect failed, err:%v", err)
			return
		}
		containerIP = ip.String()
	}

	// 记录容器信息
	containerInfo, err := container.RecordContainerInfo(parent.Process.Pid, cmdArray, containerName, containerID, volume, net, containerIP, ports)
	if err != nil {
		logrus.Errorf("record container info failed, err:%v", err)
		return
	}

	// 在子进程创建后才能通过 pipe 来发送参数
	sendInitCommand(cmdArray, writePipe)

	// 然后创建一个 goroutine 来处理后台运行的清理工作
	go func() {
		if !tty { // 后台运行程序
			// 等待子进程退出
			_, _ = parent.Process.Wait()
		}
		// 清理工作
		container.DeleteWorkSpace(containerID, volume)
		container.DeleteContainerInfo(containerID)
		if net != "" {
			network.Disconnect(net, containerInfo)
		}

		// 销毁 cgroup
		cgroupManager.Destroy()
	}()
	if tty {
		_ = parent.Wait() // 前台运行，等待容器进程结束
	}
}

// sendInitCommand 用于将用户指定的命令转换为字符串，通过 writePipe 将指令发送给子进程
func sendInitCommand(cmdArray []string, writePipe *os.File) {
	command := strings.Join(cmdArray, " ")
	logrus.Infof("command all is %s", command)
	_, _ = writePipe.WriteString(command)
	_ = writePipe.Close()
}
